/*
 * @(#)RepositoryRetrieveService.java
 * Date 2013-08-30
 * Version 1.0
 * Author Jim Horner
 * Copyright (c)2012
 */


package us.hornerscorners.lamppost.repository.service.es;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;

import javax.enterprise.context.ApplicationScoped;

import javax.enterprise.inject.Alternative;

import javax.inject.Inject;

import ihe.iti.xds_b._2007.RetrieveDocumentSetRequestType;

import ihe.iti.xds_b._2007.RetrieveDocumentSetRequestType.DocumentRequest;

import ihe.iti.xds_b._2007.RetrieveDocumentSetResponseType;

import ihe.iti.xds_b._2007.RetrieveDocumentSetResponseType.DocumentResponse;

import org.apache.commons.lang3.StringUtils;

import org.elasticsearch.index.query.IdsQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import us.hornerscorners.lamppost.exception.ServiceActionError;
import us.hornerscorners.lamppost.exception.ServiceActionException;
import us.hornerscorners.lamppost.exception.SeverityType;

import us.hornerscorners.lamppost.ihecommon.config.IHEConfig;
import us.hornerscorners.lamppost.ihecommon.config.IHEServiceType;
import us.hornerscorners.lamppost.ihecommon.config.RepositoryServiceConfig;

import us.hornerscorners.lamppost.ihecommon.service.ElasticSearchSource;

import us.hornerscorners.lamppost.model.audit.AuditContext;

import us.hornerscorners.lamppost.model.document.DocumentContent;

import us.hornerscorners.lamppost.registry.constant.ErrorCode;
import us.hornerscorners.lamppost.registry.constant.ResponseStatus;

import us.hornerscorners.lamppost.registry.model.RegistryResponse;

import us.hornerscorners.lamppost.registry.transformer.xds
    .RegistryResponseTypeTransformer;

import us.hornerscorners.lamppost.repository.inject.RepositoryTarget;

import us.hornerscorners.lamppost.repository.service.AtnaRepositoryService;
import us.hornerscorners.lamppost.repository.service.IRepositoryRetrieveService;

import us.hornerscorners.lamppost.utils.OIDUtils;


/**
 * Class description
 *
 *
 * @version        v1.0, 2013-08-30
 * @author         Jim Horner
 */
@ApplicationScoped
@Alternative
public class RepositoryRetrieveService implements IRepositoryRetrieveService {

    /** Field description */
    @Inject
    private AtnaRepositoryService atnaService;

    /** Field description */
    @Inject
    private IHEConfig config;

    /** Field description */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /** Field description */
    @Inject
    @RepositoryTarget
    private RegistryResponseTypeTransformer responseTypeTransformer;

    /** Field description */
    private RepositoryServiceConfig serviceConfig;

    /** Field description */
    @Inject
    private ElasticSearchSource source;

    /**
     * Constructs ...
     *
     */
    public RepositoryRetrieveService() {
        super();
    }

    /**
     * Method description
     *
     *
     * @param missingDocs
     *
     * @return
     */
    private Collection<ServiceActionError> createMissingErrors(
            HashSet<String> missingDocs) {

        List<ServiceActionError> result = new ArrayList<>();

        for (String missingDoc : missingDocs) {

            String msg = String.format("Document [%s] is not available.", missingDoc);

            result.add(
                new ServiceActionError(
                    ErrorCode.XDSDocumentUniqueIdError.getCode(), msg,
                    SeverityType.Error));
        }

        return result;
    }

    /**
     * Method description
     *
     */
    @PostConstruct
    public void postConstruct() {

        this.serviceConfig = (RepositoryServiceConfig) this.config.findServiceConfig(
            IHEServiceType.Repository);
    }

    /**
     * ITI-43 Retrieve Document Set
     *
     *
     * @param body
     *
     * @return
     */
    @Override
    public RetrieveDocumentSetResponseType retrieveDocument(AuditContext auditContext,
            RetrieveDocumentSetRequestType body) {

        List<ServiceActionError> errors = new ArrayList<>();

        RetrieveDocumentSetResponseType result = new RetrieveDocumentSetResponseType();

        String homeCommunityId = this.config.getHomeCommunityId();

        String repoUniqueId = this.serviceConfig.getId();

        Map<String, DocumentRequest> requestMap = new HashMap<>();

        IdsQueryBuilder query = QueryBuilders.idsQuery();

        for (DocumentRequest request : body.getDocumentRequest()) {

            ServiceActionError homeError = validateHome(homeCommunityId,
                                               request.getHomeCommunityId());

            if (homeError != null) {
                errors.add(homeError);
            }

            ServiceActionError repoError = validateRepo(repoUniqueId,
                                               request.getRepositoryUniqueId());

            if (repoError != null) {

                errors.add(repoError);

            }

            if ((homeError == null) && (repoError == null)) {

                String uniqueId = request.getDocumentUniqueId();

                query.addIds(OIDUtils.createURN(uniqueId));
                requestMap.put(uniqueId, request);
            }
        }

        Set<String> foundDocs = new HashSet<>();

        try {

            List<DocumentContent> docs = this.source.runQuery(query,
                                             DocumentContent.class);

            for (DocumentContent doc : docs) {

                String uniqueId = doc.getUniqueId().getRoot();

                foundDocs.add(uniqueId);

                DocumentRequest docRequest = requestMap.get(uniqueId);

                DocumentResponse docResponse = new DocumentResponse();

                docResponse.setDocument(doc.getContent());
                docResponse.setMimeType(doc.getMimeType());

                docResponse.setDocumentUniqueId(docRequest.getDocumentUniqueId());
                docResponse.setHomeCommunityId(docRequest.getHomeCommunityId());
                docResponse.setRepositoryUniqueId(docRequest.getRepositoryUniqueId());

                result.getDocumentResponse().add(docResponse);
            }

            HashSet<String> totalDocs = new HashSet<>();

            totalDocs.addAll(requestMap.keySet());
            totalDocs.removeAll(foundDocs);

            if (totalDocs.isEmpty() == false) {

                errors.addAll(createMissingErrors(totalDocs));
            }

            RegistryResponse response = null;

            if (errors.isEmpty()) {

                response = new RegistryResponse(ResponseStatus.Success);

            } else {

                ServiceActionException ex = new ServiceActionException(getClass(),
                                                errors);

                if (totalDocs.isEmpty()) {

                    response = new RegistryResponse(ResponseStatus.PartialSuccess, ex);

                } else {

                    response = new RegistryResponse(ResponseStatus.Failure, ex);
                }
            }

            result.setRegistryResponse(this.responseTypeTransformer.transform(response));

        } catch (ServiceActionException e) {

            // create a response
            logger.error(e.getMessage(), e);

            RegistryResponse response = new RegistryResponse(ResponseStatus.Failure, e);

            result.setRegistryResponse(this.responseTypeTransformer.transform(response));
        }

        this.atnaService.sendRetrieveReceivedAuditMessage(auditContext, result);

        return result;
    }

    /**
     * Method description
     *
     *
     * @param homeCommunityId
     * @param requestHome
     *
     * @return
     */
    private ServiceActionError validateHome(String homeCommunityId, String requestHome) {

        ServiceActionError result = null;

        if (StringUtils.isNotBlank(requestHome)
                && (homeCommunityId.equals(requestHome) == false)) {

            String msg = String.format("Home Community Id [%s] is not recognized.",
                                       requestHome);

            result = new ServiceActionError(ErrorCode.XDSUnknownCommunity.getCode(), msg,
                                            SeverityType.Error);
        }

        return result;
    }

    /**
     * Method description
     *
     *
     * @param repoUniqueId
     * @param requestRepo
     *
     * @return
     */
    private ServiceActionError validateRepo(String repoUniqueId, String requestRepo) {

        ServiceActionError result = null;

        if (repoUniqueId.equals(requestRepo) == false) {

            String msg = String.format("Repository Unique Id [%s] is not recognized.",
                                       requestRepo);

            result = new ServiceActionError(ErrorCode.XDSUnknownRepositoryId.getCode(),
                                            msg, SeverityType.Error);
        }

        return result;
    }
}
